#include "Grid.h"
#include "GlobalVariables.h"
#include "Input/InputManager.h"
#include <ctime>

Grid::Grid(void):
	Object()
	,m_TileWidth(10)
	,m_FieldWidth(130)
	,m_Multiplier(1)
	,m_CurrentTileID(0)
	,m_Colom(-1)
	,m_TilesToDelete()
	,m_FoundIDs()
	,m_PreviousIDs()
	,m_Bottom(true)
	,m_Top(true)
	,m_Left(true)
	,m_Right(true)
	,m_BottomEdge(true)
	,m_TopEdge(true)
	,m_LeftEdge(true)
	,m_RightEdge(true)
	,m_GameOver(false)
	,m_FoundIDPrevColom(false)
	,m_LineAdded(true)
{
	m_TilesToDelete.clear();
	m_FoundIDs.clear();
	m_PreviousIDs.clear();
}


Grid::~Grid(void)
{
}

void Grid::Initialize()
{
	//Setting a random seed
	srand (uint32(time(NULL)) );
	//Throw away first random value, tend to be the same (say people on the internet)
	rand();

	GenerateLevel();

	m_pLineObj = new Line();
	m_pLineObj->SetBorder(LEFT_BORDER,RIGHT_BORDER);
	AddChild(m_pLineObj);

}

void Grid::AfterInitialzed()
{
	m_FieldWidth = m_TileWidth * MAX_TILES_WIDTH;

	for(int r = 0; r < MAX_TILES_HEIGHT; ++r)
	{
		for(int c = 0; c < MAX_TILES_WIDTH; ++c)
		{
			CheckForSquare(c,r);
		}
	}
}

void Grid::SwichtTileWithRightSide(const ivec2& leftBottomPos)
{
	ASSERT(leftBottomPos.x >= 0 && leftBottomPos.y >= 0, _T("Positive positions pls"));
	SwichtTileWithRightSide(leftBottomPos.x, leftBottomPos.y);
}

void Grid::SwichtTileWithRightSide(uint8 left, uint8 bottom)
{
	ASSERT(left + 1 <= MAX_TILES_WIDTH, _T("pos should be smaller than Max Width - 1"));
	ASSERT(bottom <= MAX_TILES_HEIGHT, _T("pos should be smaller than Max height"));

	if(m_TileArr[left][bottom]->GetTileID() == 0 && m_TileArr[left + 1][bottom]->GetTileID() == 0)
	{
		SwichTiles(left, bottom, left+1, bottom);
		ivec2  firstCoords = CheckGravity(left, bottom);
		ivec2  secondCoords = CheckGravity(left + 1 , bottom);

		CheckForSquare(firstCoords);
		CheckForSquare(secondCoords);
	}
	
}

void Grid::SwichTiles(uint8 width1, uint8 height1, uint8 width2, uint8 height2)
{
	auto temp = m_TileArr[width1][height1];
	m_TileArr[width1][height1] = m_TileArr[width2][height2];
	m_TileArr[width2][height2] = temp;

	auto tempPos = m_TileArr[width1][height1]->GetTransform()->GetLocalPosition();
	m_TileArr[width1][height1]->GetTransform()->Translate(
		m_TileArr[width2][height2]->GetTransform()->GetLocalPosition());
	m_TileArr[width2][height2]->GetTransform()->Translate(tempPos);
}

ivec2 Grid::CheckGravity(uint8 width, uint8 height)
{
	ivec2 returnPos(width, height);
	if(m_TileArr[width][height]->IsVisible())
	{
		if(m_TileArr[width][height]->GetType() == TileType::NORMAL)
		{
			if(height > 0)
			{
				if(!m_TileArr[width][height - 1]->IsVisible())
				{
					SwichTiles(width, height, width, height - 1);
					returnPos = CheckGravity(width, height - 1);
				}
			}		
		}
	}
	else
	{
		if(height < MAX_TILES_HEIGHT-1)
		{
			if(m_TileArr[width][height + 1]->IsVisible()
				&& m_TileArr[width][height + 1]->GetType() == TileType::NORMAL)
			{
				SwichTiles(width, height, width, height + 1);
				CheckGravity(width, height + 1);
			}
			else
			{
				CheckGravity(width, height + 1);
			}	
		}
	}	
	return returnPos;
}

void Grid::CheckGlobalGravity()
{
	for(int col = 1; col < MAX_TILES_HEIGHT; ++col)
	{
		for(int row = 0; row < MAX_TILES_WIDTH; ++row)
		{
			int lower(col - 1);
			while(lower >= 0 && m_TileArr[row][lower + 1]->IsVisible() && 
				m_TileArr[row][lower + 1]->GetType() == TileType::NORMAL && !m_TileArr[row][lower]->IsVisible())
			{
				SwichTiles(row, lower + 1, row, lower);
				--lower;					
			}
		}
	}
}

uint8 Grid::GetNrOfHorizontalTiles() const
{
	return MAX_TILES_WIDTH;
}

uint8 Grid::GetNrOfVerticalTiles() const
{
	return MAX_TILES_HEIGHT;
}

uint8 Grid::GetSafeZone() const
{
	return SAFE_ZONE_LIMIT;
}

uint8 Grid::GetNrOfTilesInColumn(uint8 col)
{
	int iter(0);
	for(int i = 0; i < MAX_TILES_HEIGHT; ++i)
	{
		if(m_TileArr[col][i]->IsVisible())
		{
			++iter;
		}
	}
	return iter;
}

void Grid::CheckForSquare(const ivec2& coords)
{
	CheckForSquare(coords.x , coords.y);
}

void Grid::CheckForSquare(uint8 width, uint8 height)
{
	CheckAdjacentTilesForID(width, height);

	if(!m_LeftEdge)
	{
		if(m_TileArr[width - 1][height]->IsVisible() && m_TileArr[width][height]->IsVisible())
		{
			//Current and one left of it are same color!
			if(CompareTileColors(width - 1, height, width, height))
			{
				//Check tiles above and below 2 matching tiles.
				if(!m_BottomEdge)
				{			
					if(m_TileArr[width - 1][height - 1]->IsVisible() && m_TileArr[width][height - 1]->IsVisible())
					{
						if(CompareTileColors(width - 1, height - 1, width, height) && 
							CompareTileColors(width, height - 1, width, height))
						{
							if(!m_Left)
							{
								int8 adjacentTileID(m_TileArr[width - 1][height]->GetTileID());
								MakeSquare(width - 1, height - 1, width, height, adjacentTileID);
							}
							else if(!m_Bottom)
							{
								int8 adjacentTileID(m_TileArr[width][height - 1]->GetTileID());
								MakeSquare(width - 1, height - 1, width, height, adjacentTileID);
							}
							else
							{
								MakeSquare(width - 1, height - 1, width, height, MakeNewTileID());
							}	
							CheckAdjacentTilesForID(width, height);
						}
					}
				}
				if(!m_TopEdge)
				{
					if(m_TileArr[width - 1][height + 1]->IsVisible() && m_TileArr[width][height + 1]->IsVisible())
					{
						if(CompareTileColors(width - 1, height + 1, width, height) && 
							CompareTileColors(width, height + 1, width, height))
						{								
							if(!m_Left)
							{
								int8 adjacentTileID(m_TileArr[width - 1][height]->GetTileID());
								MakeSquare(width - 1, height, width, height + 1, adjacentTileID);
							}
							else if(!m_Top)
							{
								int8 adjacentTileID(m_TileArr[width][height + 1]->GetTileID());
								MakeSquare(width - 1, height, width, height + 1, adjacentTileID);
							}
							else
							{
								MakeSquare(width - 1, height, width, height + 1, MakeNewTileID());
							}	
							CheckAdjacentTilesForID(width, height);
						}
					}
				}
			}
		}
	}

	if(!m_RightEdge)
	{
		if(m_TileArr[width + 1][height]->IsVisible() && m_TileArr[width][height]->IsVisible())
		{
			//Current and one left of it are same color!
			if(CompareTileColors(width + 1, height, width, height))
			{
				//Check tiles above and below 2 matching tiles.
				if(!m_BottomEdge)
				{					
					if(m_TileArr[width + 1][height - 1]->IsVisible() && m_TileArr[width][height - 1]->IsVisible())
					{
						if(CompareTileColors(width + 1, height - 1, width, height) && 
							CompareTileColors(width, height - 1, width, height))
						{
							if(!m_Right)
							{
								int8 adjacentTileID(m_TileArr[width + 1][height]->GetTileID());
								MakeSquare(width, height - 1, width + 1, height, adjacentTileID);
							}
							else if(!m_Bottom)
							{
								int8 adjacentTileID(m_TileArr[width][height - 1]->GetTileID());
								MakeSquare(width, height - 1, width + 1, height, adjacentTileID);
							}
							else
							{
								MakeSquare(width, height - 1, width + 1, height, MakeNewTileID());
							}		
							CheckAdjacentTilesForID(width, height);
						}
					}
				}
				if(!m_TopEdge)
				{
					if(m_TileArr[width + 1][height + 1]->IsVisible() && m_TileArr[width][height + 1]->IsVisible())
					{
						if(CompareTileColors(width + 1, height + 1, width, height) && 
							CompareTileColors(width, height + 1, width, height))
						{
							if(!m_Right)
							{
								int8 adjacentTileID(m_TileArr[width + 1][height]->GetTileID());
								MakeSquare(width, height, width + 1, height + 1, adjacentTileID);
							}
							else if(!m_Top)
							{
								int8 adjacentTileID(m_TileArr[width][height + 1]->GetTileID());
								MakeSquare(width, height, width + 1, height + 1, adjacentTileID);
							}
							else
							{
								MakeSquare(width, height, width + 1, height + 1, MakeNewTileID());
							}
							CheckAdjacentTilesForID(width, height);
						}
					}
						
				}
			}
		}
	}
}

void Grid::CheckForSquareGlobal()
{
	for(int col = 0; col < MAX_TILES_HEIGHT; ++col)
	{
		for(int row = 0; row < MAX_TILES_WIDTH; ++row)
		{
			CheckForSquare(row, col);			
		}
	}
}
void Grid::Update( const star::Context& context )
{
	if(m_pLineObj != nullptr)
	{
		vec2 pos(m_pLineObj->GetPosition());

		int32 col((int32(pos.x) / m_TileWidth));
		if(col != m_Colom)
		{
			m_Colom=col;
			int8 numtiles =0;
			if(m_Colom >= 0 && m_Colom < MAX_TILES_WIDTH)
			{
				//Old system only loops over the visible numbers
				//Cur system loop over entire colom
				bool bfoundID =false;
				for(int i = 0; i < SAFE_ZONE_LIMIT; ++i)
				{
					//Check if the tile is visible otherwise proceed to the next one
					if(!m_TileArr[m_Colom][i]->IsVisible())continue;

					uint8 ID = m_TileArr[m_Colom][i]->GetTileID();
					if(ID != 0)
					{
						bfoundID=true;
						auto it = m_FoundIDs.find(ID);
						if(it != m_FoundIDs.end())
						{
							++m_FoundIDs[ID];
						}
						else
						{
							m_FoundIDs[ID] = 1;
						}
						m_TileArr[m_Colom][i]->SetType(TileType::NOBORDER);
						//m_TilesToDelete[ID].push_back(ivec2(m_Colom,i));

					}
				}
				if(!m_FoundIDPrevColom)
				{
					if(m_Colom > 0)
						CheckColumnForGameOver(m_Colom-1);
				}
				m_FoundIDPrevColom=bfoundID;
				//If we din't find any new squares then delete all the other ones
				if(m_FoundIDs.size()==0)
				{
					for(auto& it : m_PreviousIDs)
					{
						int coltocheck = m_Colom;
						if(coltocheck==0)coltocheck = MAX_TILES_WIDTH;
						for(int c=0; c<coltocheck; ++c)
						{
							for(int h = 0; h < SAFE_ZONE_LIMIT; ++h)
							{
								if(!m_TileArr[c][h]->IsVisible())continue;
								uint8 ID = m_TileArr[c][h]->GetTileID();
								if(ID == it.first)
								{
									m_TilesToDelete[ID].push_back(ivec2(c,h));
								}
							}
						}
					
						uint16 size = m_TilesToDelete[it.first].size();
						for(int t = 0; t < size; ++t)
						{
							ivec2 arraypos = m_TilesToDelete[it.first][t];
							m_TileArr[arraypos.x][arraypos.y]->SetVisible(false);
							m_TileArr[arraypos.x][arraypos.y]->SetTileID(0);
						}
						CheckGlobalGravity();
						CheckForSquareGlobal();
						uint8 currentColumn(0);
						for(auto& it : m_TilesToDelete)
						{
							for(auto& it2 : it.second)
							{
								if(it2.x > currentColumn)
								{
									currentColumn = it2.x;
									CheckColumnForGameOver(currentColumn);
								}	
							}

						}
						m_TilesToDelete.erase(it.first);
						GlobalVariables::GetInstance()->AddScore(it.second,m_Multiplier);
						++m_Multiplier;
						
					}
					m_PreviousIDs.clear();
				}
				else
				{
					//Check if in the found ID, it was already in the previous IDs, if so add the amount
					for(auto& foundIt : m_FoundIDs)
					{
						auto& prevIt = m_PreviousIDs.find(foundIt.first);
						if(prevIt!=m_PreviousIDs.end())
						{
							prevIt->second+=foundIt.second;
						}
						else
						{
							m_PreviousIDs[foundIt.first]=foundIt.second;
						}
					}
					m_FoundIDs.clear();
				}				
			}
		}

		//Reset score multiplier when the sweep line resets
		if(pos.x > RIGHT_BORDER || pos.x<0)
		{
			if(!m_LineAdded)
			{
				AddLine();
				m_LineAdded=true;
			}

			m_Multiplier=1;
		}
		else
		{
			m_LineAdded=false;
		}
	}
}

bool Grid::CompareTileColors(uint8 width1, uint8 height1, uint8 width2, uint8 height2)
{
	return m_TileArr[width1][height1]->GetColor() == m_TileArr[width2][height2]->GetColor();
}

void Grid::MakeSquare(uint8 left, uint8 bottom, uint8 right, uint8 top, int8 tileID)
{
		m_TileArr[left][top]->SetType(TileType::TOPLEFT);
		m_TileArr[left][bottom]->SetType(TileType::BOTTOMLEFT);
		m_TileArr[right][top]->SetType(TileType::TOPRIGHT);
		m_TileArr[right][bottom]->SetType(TileType::BOTTOMRIGHT);

		SetTileIDs(left, bottom, left, top, tileID);
		SetTileIDs(right, bottom, right, top, tileID);
}

void Grid::SetTileIDs(uint8 left1, uint8 bottom1, uint8 left2, uint8 bottom2, int8 tileID)
{
	m_TileArr[left1][bottom1]->SetTileID(tileID);
	m_TileArr[left2][bottom2]->SetTileID(tileID);
}

int8 Grid::MakeNewTileID()
{
	star::Logger::GetInstance()->Log(star::LogLevel::Info, _T("Incrementing tile ID"));
	return ++m_CurrentTileID;
}

void Grid::CheckAdjacentTilesForID(int8 width, int8 height)
{
	m_LeftEdge = true;
	m_RightEdge = true;
	m_TopEdge = true;
	m_BottomEdge = true;

	m_Top = true;
	m_Bottom = true;
	m_Left = true;
	m_Right = true;

	if(width > 0)
	{
		m_LeftEdge = false;
		m_Left = m_TileArr[width - 1][height]->GetTileID() == 0;
	}
	if(width < MAX_TILES_WIDTH - 1)
	{
		m_RightEdge = false;
		m_Right = m_TileArr[width + 1][height]->GetTileID() == 0;
	}
	if(height > 0)
	{
		m_BottomEdge = false;
		m_Bottom = m_TileArr[width][height - 1]->GetTileID() == 0;
	}
	if(height < MAX_TILES_HEIGHT - 1)
	{
		m_TopEdge = false;
		m_Top = m_TileArr[width][height + 1]->GetTileID() == 0;
	}
}
void Grid::GenerateLevel()
{
	TileColor col;
	for(int r = 0; r < MAX_TILES_HEIGHT; ++r)
	{
		for(int c = 0; c < MAX_TILES_WIDTH; ++c)
		{
			int randNum = rand()%2;

			switch(randNum)
			{
			case 0:
				col = TileColor::BLACKTILE;
				break;
			case 1:
				col = TileColor::WHITETILE;
				break;
			default:
				col = TileColor::BLACKTILE;
				break;
			}
			m_TileArr[c][r] = new BaseTile(TileType::NORMAL, col, ivec2((c * 10),(r *  10)));
			m_TileArr[c][r]->SetVisible(false);
		    AddChild(m_TileArr[c][r]);
		}
	}


	int lWidthBound = 8;
	int hWidthBound = MAX_TILES_WIDTH - 2;

	int lHeightBound = 2;
	int hHeightBound = SAFE_ZONE_LIMIT - 4;

	int randWidth	=	lWidthBound + (rand() % (hWidthBound-lWidthBound) + 1);

	//Get the starting position to begin the level
	int startXoffset = ((MAX_TILES_WIDTH / 2) - (randWidth / 2));
	for(int c = 0; c < randWidth; ++c)
	{
		int randHeight	=	lHeightBound + (rand() % (hHeightBound-lHeightBound)+ 1);
		for(int r = 0; r < randHeight; ++r)
		{
			m_TileArr[startXoffset+c][r]->SetVisible(true);
		}
	}


	//m_TileArr[2][SAFE_ZONE_LIMIT]->SetVisible(true);
}

void Grid::ReGenerateLevel()
{
	TileColor col;
	for(int r = 0; r < MAX_TILES_HEIGHT; ++r)
	{
		for(int c = 0; c < MAX_TILES_WIDTH; ++c)
		{
			int randNum = rand()%2;

			switch(randNum)
			{
			case 0:
				col = TileColor::BLACKTILE;
				break;
			case 1:
				col = TileColor::WHITETILE;
				break;
			default:
				col = TileColor::BLACKTILE;
				break;
			}
			m_TileArr[c][r]->SetVisible(false);
			m_TileArr[c][r]->SetType(TileType::NORMAL);
		}
	}


	int lWidthBound = 8;
	int hWidthBound = MAX_TILES_WIDTH - 2;

	int lHeightBound = 2;
	int hHeightBound = SAFE_ZONE_LIMIT - 4;

	int randWidth	=	lWidthBound + (rand() % (hWidthBound-lWidthBound) + 1);

	//Get the starting position to begin the level
	int startXoffset = ((MAX_TILES_WIDTH / 2) - (randWidth / 2));
	for(int c = 0; c < randWidth; ++c)
	{
		int randHeight	=	lHeightBound + (rand() % (hHeightBound-lHeightBound)+ 1);
		for(int r = 0; r < randHeight; ++r)
		{
			m_TileArr[startXoffset+c][r]->SetVisible(true);
		}
	}

}

void Grid::CheckColumnForGameOver(uint8 col)
{
	for(uint8 i = SAFE_ZONE_LIMIT - 1; i < MAX_TILES_HEIGHT; ++i)
	{
		if(m_TileArr[col][i]->IsVisible())
		{
			m_GameOver = true;
		}
	}
}

bool Grid::GameOver() const
{
	return m_GameOver;
}

void Grid::ResetGrid()
{
	m_GameOver = false;
	m_LineAdded=true;
	m_pLineObj->SetBorder(LEFT_BORDER,RIGHT_BORDER);
	ReGenerateLevel();
}

void Grid::AddLine()
{
	int32 basewidthstart = -1;
	int32 basewidthend = 0;
	for(uint8 i=0; i<MAX_TILES_WIDTH; ++i)
	{
		//search for the first one
		if(m_TileArr[i][0]->IsVisible() && basewidthstart == -1)
		{
			basewidthstart = i;
		}
		//otherwise set lastest visible to i
		else if(m_TileArr[i][0]->IsVisible())
		{
			basewidthend = i;
		}
	}
	int32 width = basewidthend - basewidthstart;


	for (uint32 h = MAX_TILES_HEIGHT-1 ; h > 0 ; --h)
	{
		for(uint32 c = 0; c < MAX_TILES_WIDTH ; ++c)
		{
			SwichTiles(c,h,c,h-1);
		}
	}

	int leftrandomadd = rand()%(basewidthstart+1);
	int rightrandomadd = rand()%(MAX_TILES_WIDTH - basewidthend);

	TileColor col;
	for(int32 n = 0; n <= width+rightrandomadd+leftrandomadd ; ++n)
	{
		int randNum = rand()%2;
		switch(randNum)
		{
		case 0:
			col = TileColor::BLACKTILE;
			break;
		case 1:
			col = TileColor::WHITETILE;
			break;
		default:
			col = TileColor::BLACKTILE;
			break;
		}

		m_TileArr[(basewidthstart-leftrandomadd)+n][0]->SetVisible(true);
		m_TileArr[(basewidthstart-leftrandomadd)+n][0]->SetType(TileType::NORMAL);
		m_TileArr[(basewidthstart-leftrandomadd)+n][0]->SetColor(col);
	}
	CheckForSquareGlobal();

}
